package router

import "C"
import (
	"database/sql"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	logg "log"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/tungyao/cedar"
	"github.com/tungyao/spruce"

	"../mode"
	_ "github.com/go-sql-driver/mysql"
	// awesome_pool "github.com/tungyao/awesome-pool"
	// "github.com/tungyao/spruce"
	"github.com/tungyao/tjson"
)

var (
	D   *sql.DB
	P   *spruce.Hash
	err error
	log *logg.Logger
)

func init() {
	fs, errx := os.OpenFile("./log/blog_server.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 666)
	if errx != nil {
		logg.Fatalln(errx)
	}
	log = logg.New(fs, "", logg.LstdFlags|logg.Lshortfile|logg.LUTC)
	D, err = sql.Open("mysql", fmt.Sprintf("%s:%s@%s(%s:%s)/%s?charset=utf8mb4", "root", "1121331", "tcp", "localhost", "3306", "blog"))
	if err != nil {
		log.Panicln(err)
	}
	UpdateAliveArt()
	P = spruce.CreateHash(4096)
}
func Template(w http.ResponseWriter, path string, content ...string) {
	fs, err := os.Open(path)
	if err != nil {
		log.Println(err)
		w.WriteHeader(503)
		return
	}
	d, err := ioutil.ReadAll(fs)
	if err != nil {
		log.Println(56, err)
		w.WriteHeader(503)
		return
	}
	if len(content) >= 2 {
		w.Header().Add(content[0], content[1])
	}
	w.Write(d)
}
func ERROR(w http.ResponseWriter, msg string) {
	w.WriteHeader(503)
	w.Write([]byte(msg))
}
func LOGERROR(w http.ResponseWriter, err2 error) {
	if err != nil {
		w.WriteHeader(503)
		w.Write([]byte(err.Error()))
		log.Panicln(err)
	}
}
func SUCCESS(w http.ResponseWriter, msg string) {
	w.Write([]byte(msg))
}
func JSONSUCC(w http.ResponseWriter, v interface{}) {
	out, err := json.Marshal(v)
	if err != nil {
		log.Println(err)
		w.WriteHeader(503)
		return
	}
	w.Header().Add("content-type", "application/json")
	w.Write(out)
}
func Index(writer http.ResponseWriter, request *http.Request, co *cedar.Core) {
	Template(writer, "./static/dist/index.html")
}
func ArticlesList(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	w.Header().Add("content-type", "application/json")
	o, _ := QueryRows(w, r, 0).MarshalJSON()
	w.Write(o)
}

func CategoryList(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	rows, err := D.Query("select * from category where father=0")
	defer rows.Close()
	if err != nil {
		ERROR(w, err.Error())
		return
	}
	data := make([]mode.Category, 0)
	x := mode.GetCategoryStruct()
	for rows.Next() {
		rows.Scan(&x.Id, &x.Father, &x.Name, &x.Color)
		data = append(data, *x)
	}
	JSONSUCC(w, data)
}
func updateDetail(id string) ([]byte, error) {
	st := mode.GetArticlesStruct()
	stmt, _ := D.Prepare("select * from articles where id=?")
	row := stmt.QueryRow(id)
	row.Scan(&st.Id, &st.Uid, &st.Author, &st.Title, &st.Body, &st.Create_time, &st.Update_time)

	return json.Marshal(st)

}
func PageDetail(w http.ResponseWriter, r *http.Request, co *cedar.Core) {

	tp, err := template.ParseFiles("./template/front/detail.html")
	if err != nil {
		log.Panicln(err)
	}
	var art = mode.GetArticlesStruct()
	stmt, err := D.Prepare("select id,title,body from articles where id=? or title like ? and is_delete=0")
	if err != nil {
		ERROR(w, err.Error())
		return
	}
	row := stmt.QueryRow(r.URL.Fragment, "%"+r.URL.Fragment+"%")

	row.Scan(&art.Id, &art.Title, &art.Body)
	if nTitle := P.Get(spruce.MD5([]byte(strconv.Itoa(art.Id)))); nTitle != nil {
		art.Title = nTitle.(string)
	} else {
		req, err := http.NewRequest("GET", "http://120.26.6.172/get.php?source="+art.Title+"&param1=0&param2=0", nil)
		res, err := http.DefaultClient.Do(req)
		if err == nil {
			allReqData, err := ioutil.ReadAll(res.Body)
			if err != nil {
				log.Panicln(err)
			}
			defer res.Body.Close()
			sStr := SplitString(allReqData, []byte{13, 10})
			// oldTitle := art.Title
			for _, v := range sStr[:len(sStr)-1] {
				art.Title += "," + string(v)
			}
			P.Set(spruce.MD5([]byte(strconv.Itoa(art.Id))), art.Title, 0)
		}
	}
	if art.Title == "" {
		http.NotFound(w, r)
	} else {
		art.BodyHtml = template.HTML(art.Body)
		tp.Execute(w, art)
	}
}
func PostArticle(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	if id := r.FormValue("id"); id != "" {
		stmt, _ := D.Prepare("update articles set uid=?,author=?,title=?,origin_body=?,body=?,update_time=?,category=?,is_delete=? where id=?")
		res, err := stmt.Exec(
			r.FormValue("uid"),
			r.FormValue("author"),
			r.FormValue("title"),
			r.FormValue("origin_body"),
			r.FormValue("body"),
			time.Now().Unix(),
			r.FormValue("category"),
			r.FormValue("is_delete"),
			id,
		)
		if err != nil {
			ERROR(w, err.Error())
			return
		}
		// if da, errs := updateDetail(id); errs == nil {
		// conn := P.Get()
		// conn.Write(spruce.EntryHashGet(spruce.EntryHashSet([]byte("article"+id), da, 21600)))
		// }
		in, _ := res.RowsAffected()
		ix, _ := strconv.Atoi(id)
		Monitor <- ix
		go UpdateAliveArt()
		SUCCESS(w, strconv.Itoa(int(in)))
		return
	}
	stmt, err := D.Prepare("insert into articles set uid=?,author=?,title=?,origin_body=?,body=?,create_time=?,update_time=?,category=?,is_delete=?")
	if err != nil {
		ERROR(w, err.Error())
		return
	}
	res, err := stmt.Exec(
		r.FormValue("uid"),
		r.FormValue("author"),
		r.FormValue("title"),
		r.FormValue("origin_body"),
		r.FormValue("body"),
		time.Now().Unix(),
		time.Now().Unix(),
		r.FormValue("category"),
		r.FormValue("is_delete"),
	)
	if err != nil {
		ERROR(w, err.Error())
		return
	}
	in, _ := res.LastInsertId()
	Monitor <- int(in)
	go UpdateAliveArt()
	SUCCESS(w, strconv.Itoa(int(in)))
}
func PostCategory(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	data, _ := ioutil.ReadAll(r.Body)
	sear, err := tjson.Decode(data)
	if err != nil {
		log.Panicln(err)
	}
	if sear["id"] != "-1" {
		stmt, err := D.Prepare("update category set name=?,color=? where id=?")
		LOGERROR(w, err)
		res, err := stmt.Exec(sear["name"], sear["color"], sear["id"])
		LOGERROR(w, err)
		JSONSUCC(w, res)
		return
	}
	stmt, err := D.Prepare("insert into category set name=?,color=?")
	LOGERROR(w, err)
	res, err := stmt.Exec(sear["name"], sear["color"])
	LOGERROR(w, err)
	id, _ := res.LastInsertId()
	JSONSUCC(w, id)
}
func EditArticle(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	user := r.URL.Query().Get("user")
	pwd := r.URL.Query().Get("pwd")
	rPwd := r.URL.Query().Get("rand")
	token := r.URL.Query().Get("token")
	if token != "" {
		goto isToken
	}
	token = r.Header.Get("session_token")
	if token != "" {
		goto isToken
	}
	if user == "" || pwd == "" || rPwd == "" {
		w.Write([]byte(`<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script>
    let user = prompt("username");
    let pwd = prompt("password");
    let rands = prompt("two pass");
    let req = new XMLHttpRequest();
	req.open('GET', document.location+"?user="+user+"&pwd="+pwd+"&rand="+rands, true);
	req.send(null);
	req.onreadystatechange = function(){
		if (req.readyState == 4) {
			  if (req.status == 200) {
					sessionStorage.setItem("session_token",req.getResponseHeader("session_token"));
					location.href=location.href+"?token="+req.getResponseHeader("session_token");
				}
		}
	};
    </script>
</head>
<body>
</body>
</html>`))
		return
	}
	goto isToken
isToken:
	x := &Login{}
	if u := GetSession([]byte(token)); u != nil {
		uc := &Login{}
		JsonDecode(uc, u)
		x.Pub = uc.Pub
		x.Name = uc.Name
		x.Uid = uc.Uid
		log.Println("type", "session", x.Name)
	} else {
		stmt, _ := D.Prepare("select a.pub,b.name,b.id from double_pass a left join user b on b.id=a.uid where b.name=? and b.pwd=?")
		row := stmt.QueryRow(user, pwd)
		row.Scan(&x.Pub, &x.Name, &x.Uid)
		goto auth
	}
	if IsZero(x) {
		nt := NewToken([]byte(x.Name))
		SetSession([]byte(nt), JsonEncode(x))
		Template(w, "./static/edit.html", "session_token", string(nt))
		return
	}
auth:
	if NewGoogleAuth().VerifyCode(x.Pub, rPwd) {
		nt := NewToken([]byte(x.Name))
		SetSession([]byte(nt), JsonEncode(x))
		log.Println("type", "google auth", x.Name)
		Template(w, "./static/edit.html", "session_token", string(nt))
		return
	}
	w.Write([]byte("<h1>双重验证失败</h1><a href='/edit'>重新登录</a>"))
}

// 也就是管理员
// 后台登录验证的 必须要双重验证
func AdminUserLogin(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	get := make([]byte, 256*2)
	n, _ := r.Body.Read(get)
	gJson, err := tjson.Decode(get[:n])
	if err != nil {
		log.Println(err)
		ERROR(w, err.Error())
		return
	}
	if login := authUser(gJson["name"], gJson["pwd"]); login != nil {
		nt := NewToken([]byte(login.Name))
		co.Session.Set("login", nt)
		SetSession(nt, JsonEncode(login))
		SUCCESS(w, "")
		return
	}
	ERROR(w, "auth failed")
}
func LoginOut(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	co.Session.Flush("login")
	http.Redirect(w, r, "/manager/page/login", 302)
}
func UserLogin(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	get := make([]byte, 256*2)
	n, _ := r.Body.Read(get)
	gJson, err := tjson.Decode(get[:n])
	if err != nil {
		log.Println(err)
		ERROR(w, err.Error())
		return
	}
	stmt, _ := D.Prepare("select a.pub,b.name,b.id from double_pass a left join user b on b.id=a.uid where b.name=? and b.pwd=?")
	row := stmt.QueryRow(gJson["name"], gJson["pwd"])
	x := &Login{}
	row.Scan(&x.Pub, &x.Name, &x.Uid)
	if IsZero(x) && NewGoogleAuth().VerifyCode(x.Pub, gJson["tpwd"].(string)) {
		nt := NewToken([]byte(x.Name))
		co.Session.Set("login", nt)
		SUCCESS(w, string(nt))
		return
	}
	ERROR(w, "auth failed")
}
func ManagerArticlesGet(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	if id := r.URL.Query().Get("id"); id != "" {
		data := new(mode.ArticleBackend)
		row := D.QueryRow("select id,uid,author,title,origin_body,category,is_delete from articles where id=" + id)
		row.Scan(&data.Id, &data.Uid, &data.Author, &data.Title, &data.Origin_body, &data.Category, &data.Is_delete)
		oData, _ := data.MarshalJSON()
		w.Header().Add("content-type", "application/json")
		w.Write(oData)
		return
	}
	// 	token := r.URL.Query().Get("token")
	// 	if token != "" {
	// 		goto yesToken
	// 	}
	// 	token = r.Header.Get("session_token")
	// 	if token != "" {
	// 		goto yesToken
	// 	}
	// yesToken:
	// if u := GetSession([]byte(token)); len(u) != 0 {
	if true {
		// uc := &Login{}
		// JsonDecode(uc, u)
		page := r.URL.Query().Get("page")
		stmt, _ := D.Prepare("select id,title,create_time,is_delete from articles where uid=? limit ?,?")
		it := 1
		if page != "" {
			it, _ = strconv.Atoi(page)
		}
		itp := it * 40
		it -= 1
		rows, _ := stmt.Query(1, it, itp)
		st := mode.GetBackArticleStruct()
		data := make([]mode.BackArticle, 0)
		for rows.Next() {
			rows.Scan(&st.Id, &st.Title, &st.CreateTime, &st.Is_delete)
			data = append(data, *st)
		}
		rows.Close()
		out, _ := json.Marshal(data)
		w.Header().Add("content-type", "application/json")
		w.Write(out)
		return
	}
	ERROR(w, "get failed")
}
func ManagerCategoryGet(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	rows, err := D.Query("select * from category")
	defer rows.Close()
	if err != nil {
		ERROR(w, err.Error())
		return
	}
	data := make([]mode.Category, 0)
	x := mode.GetCategoryStruct()
	for rows.Next() {
		rows.Scan(&x.Id, &x.Father, &x.Name, &x.Color)
		data = append(data, *x)
	}
	JSONSUCC(w, data)
}
func ArticleDelete(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	get := make([]byte, 256)
	n, _ := r.Body.Read(get)
	gJson, err := tjson.Decode(get[:n])
	LOGERROR(w, err)
	stmt, _ := D.Prepare("update articles set is_delete=1 where id=?")
	_, err = stmt.Exec(gJson["id"])
	LOGERROR(w, err)
	SUCCESS(w, "DELETE SUCCESS")
	return
}
func CategoryDelete(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	stmt, err := D.Prepare("delete from category where id=?")
	LOGERROR(w, err)
	row, err := stmt.Exec(r.URL.Query().Get("id"))
	LOGERROR(w, err)
	JSONSUCC(w, row)
}
func CHECKLOGIN(r *http.Request) bool {
	if u := GetSession([]byte(r.URL.Query().Get("token"))); len(u) != 0 {
		return true
	} else {
		return false
	}
}
func AccountBookShow(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	op := r.URL.Query().Get("op")
	// all 展示所有
	// one 展示一个账单 并拉取所有的数据
	switch op {
	case "all":
		data := make([]mode.AccountBookClass, 0)
		p := mode.GetAccountBookClassStruct()
		rows, _ := D.Query("select * from account_book_class order by id desc")
		for rows.Next() {
			err := rows.Scan(&p.Id, &p.Name, &p.CreateTime)
			if err != nil {
				log.Println(err)
				continue
			}
			data = append(data, *p)
		}
		JSONSUCC(w, data)
	case "one":
		data := make([]mode.AccountBook, 0)
		cid := r.URL.Query().Get("cid")
		status := r.URL.Query().Get("status")
		if status == "" {
			status = "0"
		}
		if cid == "" {
			ERROR(w, "id have none")
			return
		}
		p := mode.GetAccountBookStruct()
		stmt, _ := D.Prepare("select id,cid,money,name,create_time from account_book where status=? and cid=?")
		rows, _ := stmt.Query(status, cid)
		for rows.Next() {
			err := rows.Scan(&p.Id, &p.Cid, &p.Money, &p.Name, &p.CreateTime)
			if err != nil {
				log.Println(err)
				continue
			}
			data = append(data, *p)
		}
		JSONSUCC(w, data)
	default:
		return
	}
}

func AccountBookAdd(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	if !CHECKLOGIN(r) {
		ERROR(w, "no login")
		return
	}
	op := r.URL.Query().Get("op")
	// 增加新的账单
	// 在账单里面增加新的记录

	switch op {
	case "newAccount":
		obj, err := READJSON(r)
		if err != nil {
			log.Println(err)
			ERROR(w, "serialize json error :"+err.Error())
			return
		}
		stmt, _ := D.Prepare("insert into account_book_class set name=?,create_time=?")
		_, err = stmt.Exec(obj["name"], time.Now().Unix())
		if err != nil {
			log.Println(err)
			ERROR(w, "insert it error :"+err.Error())
		} else {
			SUCCESS(w, "1")
		}
	case "newColumn":
		obj, err := READJSON(r)
		if err != nil {
			log.Println(err)
			ERROR(w, err.Error())
			return
		}
		stml, _ := D.Prepare("insert into account_book set cid=?,money=?,name=?,create_time=?")
		_, err = stml.Exec(obj["cid"], obj["money"], obj["name"], time.Now().Unix())
		if err != nil {
			log.Println(err)
			ERROR(w, "insert it error :"+err.Error())
		} else {
			SUCCESS(w, "1")
		}
	default:
		return
	}
}
func READJSON(r *http.Request) (map[string]interface{}, error) {
	get := make([]byte, 1025)
	n, err := r.Body.Read(get)
	if err != io.EOF {
		log.Println(err)
		return nil, err
	}
	return tjson.Decode(get[:n])
}

func AccountBookDelete(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	if !CHECKLOGIN(r) {
		ERROR(w, "no login")
		return
	}
	obj, err := READJSON(r)
	if err != nil {
		log.Println(err)
		ERROR(w, err.Error())
		return
	}
	stml, _ := D.Prepare("update account_book set status=1 where id=?")
	defer stml.Close()
	_, err = stml.Exec(obj["id"])
	if err != nil {
		log.Println(err)
		ERROR(w, err.Error())
		return
	}
	SUCCESS(w, "1")
}
func SoftwareCedar(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	Template(w, "./template/software/cedar.html")
}

func BackendIndexPage(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	Template(w, "./template/backend/index.html")
}
func BackendLoginPage(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	tp, err := template.ParseFiles("./template/backend/login.html")
	// Template(w, "./template/backend/login.html")
	if err != nil {
		log.Panicln(err)
	}
	id := spruce.CreateNewId(16)
	CreateQrPng("https://"+r.Host+"/auth/qr?code="+string(id), string(id))
	P.Set(id, make(chan *Login), 60)
	tp.Execute(w, &struct {
		Url  string
		Code string
	}{
		Url:  "/static/qr/" + string(id) + ".png",
		Code: string(id),
	})
}

func ManagerIndex(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	// 获取前一天的访问列表
	nd := time.Now().AddDate(0, 0, -1)
	limit, err := os.OpenFile("./logs/limit_"+strconv.Itoa(nd.Year())+"_"+nd.Month().String()+"_"+strconv.Itoa(nd.Day()), os.O_CREATE|os.O_RDONLY, 666)
	if err != nil {
		log.Panicln(err)
	}
	n, err := ioutil.ReadAll(limit)
	if err != nil {
		ERROR(w, err.Error())
		return
	}
	yesterday := 0
	for _, v := range n {
		if v == '\n' {
			yesterday++
		}
	}
	limit.Close()
	limit, err = os.OpenFile("./logs/limit_"+strconv.Itoa(time.Now().Year())+"_"+time.Now().Month().String()+"_"+strconv.Itoa(time.Now().Day()), os.O_CREATE|os.O_RDONLY, 666)
	if err != nil {
		log.Panicln(err)
	}
	defer limit.Close()
	n, err = ioutil.ReadAll(limit)
	if err != nil {
		ERROR(w, err.Error())
		return
	}
	today := 0
	for _, v := range n {
		if v == '\n' {
			today++
		}
	}
	x := mode.GetAPiManagerIndexCountStruct()
	row := D.QueryRow("select count(*) from articles")
	var allr int
	row.Scan(&allr)
	x.Yesterday = yesterday
	x.Today = today
	x.AllArticles = allr
	JSONSUCC(w, x)
}

func ManagerArticlesPage(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	// if !CHECKLOGIN(r) {
	//	http.Redirect(w, r, "/manager/page/login", 302)
	//	return
	// }
	Template(w, "./template/backend/articleManager.html")
}
func ManagerCategoryPage(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	// if !CHECKLOGIN(r) {
	//	http.Redirect(w, r, "/manager/page/login", 302)
	//	return
	// }
	Template(w, "./template/backend/category.html")
}
func ManagerWriteArticlePage(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	Template(w, "./static/dist/write.html")
}

func Search(w http.ResponseWriter, r *http.Request, co *cedar.Core) {
	Data := MongoSearch(r.URL.Query().Get("k"))
	tp, err := template.ParseFiles("./template/front/search.html")
	if err != nil {
		log.Println(err)
	}
	d := map[string]interface{}{
		"ALL": Data,
	}
	err = tp.Execute(w, d)
	if err != nil {
		log.Println(err)
	}
}
